package powerga.general;

import static java.lang.Math.PI;
import static java.lang.Math.abs;
import static java.lang.Math.sin;
import static org.junit.Assert.assertTrue;
import jsr166y.ForkJoinPool;

import org.jenetics.Chromosome;
import org.jenetics.CompositeAlterer;
import org.jenetics.FitnessFunction;
import org.jenetics.GeneticAlgorithm;
import org.jenetics.Genotype;
import org.jenetics.Integer64Gene;
import org.jenetics.Optimize;
import org.jenetics.PartiallyMatchedCrossover;
import org.jenetics.PermutationChromosome;
import org.jenetics.SwapMutator;
import org.jenetics.util.Factory;
import org.jenetics.util.ForkJoinEvaluator;
import org.junit.Test;

public class GAUtilsTest {
	
	private static double RADIUS = 10.0;

	@Test
	public void testExecute() {
		final int stops = 20;
		
		final FitnessFunction<Integer64Gene, Double> ff = new Function(adjacencyMatrix(stops));
		final Factory<Genotype<Integer64Gene>> gtf = Genotype.valueOf(
			new PermutationChromosome(stops)
		);
		final GeneticAlgorithm<Integer64Gene, Double> ga = GeneticAlgorithm.valueOf(gtf, ff, Optimize.MINIMUM);
		ga.setPopulationSize(300);
        ga.setAlterer(new CompositeAlterer<Integer64Gene>(
            new SwapMutator<Integer64Gene>(0.2), 
            new PartiallyMatchedCrossover<Integer64Gene>(0.3)
        ));
        
        ForkJoinPool pool = new ForkJoinPool();
        ga.setEvaluator(new ForkJoinEvaluator(pool));
        try {
    		final int generations = 500;
    		
    		GAUtils.printConfig(
    				"Traveling salesman", 
    				ga, 
    				generations, 
    				((CompositeAlterer<?>)ga.getAlterer()).getAlterers().toArray()
    			);
    		
    		GAUtils.execute(ga, generations, 50);
        } finally {
        	pool.shutdown();
        }
        assertTrue(ga != null);
	}

	private static class Function implements FitnessFunction<Integer64Gene, Double> {
		private static final long serialVersionUID = 1L;
		
		private final double[][] _adjacence;
		
		public Function(final double[][] adjacence) {
			_adjacence = adjacence;
		}
		
		@Override
		public Double evaluate(final Genotype<Integer64Gene> genotype) {
			final Chromosome<Integer64Gene> path = genotype.getChromosome();
			
			double length = 0.0;
			for (int i = 0, n = path.length(); i < n; ++i) {
				final int from = path.getGene(i).intValue();
				final int to = path.getGene((i + 1)%n).intValue();
				length += _adjacence[from][to];
			}
			return length;
		}
		
		@Override
		public String toString() {
			return "Point distance";
		}
	}
	
	/**
	 * All points in the created adjacency matrix lie on a circle. So it is easy 
	 * to check the quality of the solution found by the GA.
	 */
	private static double[][] adjacencyMatrix(int stops) {
		double[][] matrix = new double[stops][stops];
		for (int i = 0; i < stops; ++i) {
			for (int j = 0; j < stops; ++j) {
				matrix[i][j] = chord(stops, abs(i - j), RADIUS);
			}
		}
		return matrix;
	}
	
	private static double chord(int stops, int i, double r) {
		return 2.0*r*abs(sin((PI*i)/stops));
	}

}